{
    var utils = {};

    /**
     * 判断空值
     * undefined null {} "" []
     */
    utils.empty = function(exp) {
        return typeof exp === "undefined" ||
            exp === null ||
            (typeof exp === "Object" && Object.keys(exp).length === 0) ||
            (typeof exp === "string" && exp.length === 0);
    };

    utils.notEmpty = function(exp) {
        return !utils.empty(exp);
    };

    /**
     * 获取 url 中的参数；如果 url == null，则从 window.location 中获取 url 参数
     */
    utils.getUriArgs = function(url) {
        // 如果 url == null，则从 window.location 中获取 url 参数
        if (url == null) {
            url = location.href;
        }
        // 待返回的参数对象
        var uriArgs = new Object();

        // URL 解码(注意不能用escape！会导致URL中的中文参数乱码)
        var str = decodeURIComponent(url);
        str = str.split("?")[1];
        // 没有参数，返回空对象
        if (utils.empty(str)) {
            return uriArgs;
        }
        // #被称为hash符号 是因为使用了vue的路由策略 #/后带的路径不会被服务端解析 所以在获取url中参数的时候需要将#/以及其后的所有内容删除
        if (str.indexOf('#/') != -1) {
            str = str.substring(0, str.indexOf('#/'));
        }

        // 以"&"分隔各个参数
        var strs = str.split("&");

        // 逐个参数处理
        for (var i = 0; i < strs.length; i++) {
            // 取得"="两边的部分
            var pair = strs[i].split("=");
            // 填写参数
            uriArgs[pair[0]] = pair[1];
        }

        return uriArgs;
    };

    /**
     * 将序列号解析为mac地址
     * @param {String} sn
     */
    utils.getMacFromSn = function(sn) {
        let macString = sn.substring(4, sn.length - 2);
        let mac = "";
        let last2ByteString = '0x' + sn.substring(sn.length - 2);
        for (let i = 0; i < macString.length; i++) {
            if (i != 0 && i % 2 == 0) {
                mac += ":"
            }
            mac += macString[i];
        }
        let lastbyte16 = parseInt(last2ByteString);
        lastbyte16 = lastbyte16 + 2
        lastbyte16 = lastbyte16.toString(16);
        mac += ":" + lastbyte16;
        console.log(mac);
        return mac.toUpperCase();
    };

    /**
     * 
     * @Author   吴云德
     * @DateTime 2021-01-19T13:28:31+0800
     * @param    {[type]}                 sn [description]
     * @return   {[type]}                    [description]
     */
    utils.getDeviceIdFromSn = function(sn) {
        let sn4byte = sn.slice(-12);
        let snid = global.dataStruct.strInsert(sn4byte, ':', 2);
        let snids = snid.split(':');
        let newsnno = global.dataStruct.decimalNumberToHexString(parseInt(snids[5], 16) + 2)
        snids.splice(5, 1, newsnno);
        return snids.join(':');
    };

    utils.HEXCHAR = {
        '0': 0,
        '1': 1,
        '2': 2,
        '3': 3,
        '4': 4,
        '5': 5,
        '6': 6,
        '7': 7,
        '8': 8,
        '9': 9,
        '10': 'A',
        '11': 'B',
        '12': 'C',
        '13': 'D',
        '14': 'E',
        '16': 'F'
    }

    utils.getHexCharValue = function(ch) {
        if ((ch >= '0') && (ch <= '9')) {
            ch -= '0';
        } else if ((ch >= 'A') && (ch <= 'F')) {
            ch -= 55;
        } else if ((ch >= 'a') && (ch <= 'f')) {
            ch -= 87;
        } else {
            throw "非法的 HEX 字符！";
        }
        return ch;
    };

    utils.hexStringToBytes = function(hexString) {
        let bytes = [];
        for (let c = 0; c < hexString.length; c += 2) {
            bytes.push(parseInt(hexString.substr(c, 2), 16));
        }
        return bytes;
    };

    utils.byte2Long = function(byte1, offset = 0) {
        let len = Math.min(8, byte1.length - offset);
        let ret = 0;
        for (let i = 0; i < len; i++) {
            ret = (ret << 8) | (byte1[offset + i] & 0xFF);
        }
        return ret;
    }

    utils.hexStringToInt = function(hexString, offset = 0) {
        console.log(hexString);
        let bytes = [];
        for (let c = 0; c < hexString.length; c += 2) {
            bytes.push(parseInt(hexString.substr(c, 2), 16));
        }
        let len = Math.min(4, bytes.length - offset);
        let ret = 0;
        for (let i = 0; i < len; i++) {
            ret = (ret << 8) | (bytes[offset + i] & 0xFF);
        }
        return ret;
    }

    utils.BintToHexString = function(int, start = 0, len = 4) {
        let bytes = [];
        bytes[3] = (int & 0xFF);
        bytes[2] = (int >> 8 & 0xFF);
        bytes[1] = (int >> 16 & 0xFF);
        bytes[0] = (int >> 24 & 0xFF);

        let stop = Math.min(start + len, bytes.length);
        // 实际长度
        len = stop - start;
        // 非法参数
        if (len < 1) {
            return "";
        }
        let ch, buf = [];
        for (let i = 0; i < len; i++) {
            ch = bytes[start + i];
            buf[2 * i] = utils.HEXCHAR[(ch >> 4) & 0x0F];
            buf[2 * i + 1] = utils.HEXCHAR[ch & 0x0F];
            // 最后一个space不插入
        }
        return buf.join("");
    }

    /**
     * 边界值检测 判断一个点xy是否在poly坐标数组围成的图形中
     * @param {Object} x 横坐标
     * @param {Object} y 纵坐标
     * @param {Object} poly 坐标数组
     */
    utils.BoundaryDetection = function(x, y, poly) {
        var flag = false,
            fx = [],
            fy = [],
            newList = [];

        // 分离x y坐标
        for (var i = 0; i < poly.length; i++) {
            if (i % 2 == 0) {
                fx.push(poly[i]);
            } else {
                fy.push(poly[i]);
            }
        }

        var fxSort = this.arrSort2Min(fx),
            fySort = this.arrSort2Min(fy),
            maxx = fxSort[0],
            minx = fxSort[fxSort.length - 1],
            maxy = fySort[0],
            miny = fySort[fySort.length - 1];

        // 如果点击的位置坐标 <最小值 >最大值时 100%不在图形内部
        if (x < minx || x > maxx || y < miny || y > maxy) {
            // console.log('这个测试都过不了。。。直接返回false；');
            return false;
        }

        // 修改坐标格式
        for (var i = 0; i < poly.length / 2; i++) {
            var obj = {
                x: null,
                y: null
            };
            obj.x = fx[i];
            obj.y = fy[i];
            newList.push(obj);
        }

        for (var i = 0, l = newList.length, j = l - 1; i < l; j = i, i++) {
            var sx = newList[i].x,
                sy = newList[i].y,
                tx = newList[j].x,
                ty = newList[j].y;

            if (sx === x && sy === y || tx === x && ty === y) {
                return true;
            }

            if (sy < y && ty >= y || sy >= y && ty < y) {
                // 线段上与射线 Y 坐标相同的点的 X 坐标
                var xx = sx + (y - sy) * (tx - sx) / (ty - sy);

                // 点在多边形的边上
                if (xx === x) {
                    flag = true;
                    break;
                }

                // 射线穿过多边形的边界
                if (xx > x) {
                    flag = !flag;
                }
            }
        }

        return flag;
    };

    /**
     * number数组 从大到小排序
     * @param {Array} arr
     */
    utils.arrSort2Min = function(arr) {
        return arr.concat().sort(function(a, b) {
            return b - a;
        });
    };

    /**
     * 获取年-月-日 时:分:秒
     * @param {时间戳} time
     */
    utils.formatDateTime = function(time) {
        var date = null;

        if (time != undefined) {
            date = new Date(time);
        } else {
            date = new Date();
        }

        var y = date.getFullYear();
        var m = date.getMonth() + 1;
        m = m < 10 ? '0' + m : m;
        var d = date.getDate();
        d = d < 10 ? '0' + d : d;
        var h = date.getHours();
        h = h < 10 ? '0' + h : h;
        var minute = date.getMinutes();
        var second = date.getSeconds();
        minute = minute < 10 ? '0' + minute : minute;
        second = second < 10 ? '0' + second : second;
        return y + '-' + m + '-' + d + ' ' + h + ':' + minute + ':' + second;
    };

    utils.formatDateStringToTime = function(dateSrting) {
        var date = dateSrting.substring(0, 19);
        date = date.replace(/-/g, '/'); // 必须把日期'-'转为'/'
        return new Date(date).getTime();
    };

    utils.formatDateToMinute = function(date) {
        return date.getHours() * 60 + date.getMinutes();
    };

    /**
     * 获取星期
     */
    utils.formateWeek = function() {
        return "星期" + "日一二三四五六".charAt(new Date().getDay());
    };

    utils.formateDateFromMinutesToTimestamp = function(minutes) {
        let milliseconds = minutes * 60 * 1000;
        let currentTimestampe = new Date();
        let cmilliseconds = (currentTimestampe.getHours() * 3600 + currentTimestampe.getMinutes() * 60 + currentTimestampe.getSeconds()) *
            1000 + currentTimestampe.getMilliseconds();
        return (currentTimestampe.getTime() - cmilliseconds) + milliseconds
    };

    /**
     * 将分钟值 根据 大小转为分 或者时 或者天
     * @param {Number} minutes
     */
    utils.formateMinutes = function(minutes) {
        if (minutes == null || isNaN(minutes)) {
            return null;
        }
        if (minutes < 60) {
            return minutes + '分钟'
        } else if (minutes >= 60) {
            return Math.floor(minutes / 60) + "小时" + (minutes % 60) + "分钟"
        } else {
            return Math.floor(minutes / 60) + "小时";
        }
    };

    /**
     * 将分钟值 根据 大小转为分 或者时 或者天
     * @param {Number} minutes
     */
    utils.formateMinutesToHM = function(minutes) {
        if (isNaN(minutes)) {
            return null;
        }
        return (Math.floor(minutes / 60) == 0 ? '00' : Math.floor(minutes / 60)) + ":" + (minutes % 60)
    };

    utils.getFileSuffix = function(fileName) {
        if (utils.empty(fileName) || fileName.indexOf('.') == -1) {
            return null;
        }
        let fileName_segmentations = fileName.split('.');
        return fileName_segmentations[fileName_segmentations.length - 1];
    };

    /**
     * 判断当前环境 浏览器环境还是 uni/wx环境
     */
    utils.getThisRunEnvironment = function() {
        if (typeof window != 'undefined' && typeof navigator != 'undefined') {
            return 'Web';
        } else if (typeof window == 'undefined' && typeof global != 'undefined') {
            var index = null;
            // #ifdef APP-PLUS
            index = 'H5App';
            // #endif
            // #ifdef MP-WEIXIN
            index = 'Weixin';
            // #endif
            return index;
        }

        return null;
    };

    /**
     * 判断是否是浏览器 是什么浏览器
     */
    utils.getBrowserType = function() {
        if (typeof window == 'undefined' || typeof navigator == 'undefined') {
            return;
        }

        var userAgent = navigator.userAgent; // 取得浏览器的userAgent字符串
        var isOpera = userAgent.indexOf("Opera") > -1; // 判断是否Opera浏览器
        var isIE = userAgent.indexOf("compatible") > -1 && userAgent.indexOf("MSIE") > -1 && !isOpera; // 判断是否IE浏览器
        var isFF = userAgent.indexOf("Firefox") > -1; // 判断是否Firefox浏览器
        var isSafari = userAgent.indexOf("Safari") > -1 && userAgent.indexOf("Chrome") == -1; // 判断是否Safari浏览器
        var isChrome = userAgent.indexOf("Chrome") > -1; // 判断是否Chrome浏览器

        if (isIE) {
            var IE5 = false,
                IE55 = false,
                IE6 = false,
                IE7 = false,
                IE8 = false;
            var reIE = new RegExp("MSIE (\\d+\\.\\d+);");
            reIE.test(userAgent);

            var fIEVersion = parseFloat(RegExp["$1"]);
            IE55 = fIEVersion == 5.5;
            IE6 = fIEVersion == 6.0;
            IE7 = fIEVersion == 7.0;
            IE8 = fIEVersion == 8.0;

            if (IE55) {
                return "IE55";
            }
            if (IE6) {
                return "IE6";
            }
            if (IE7) {
                return "IE7";
            }
            if (IE8) {
                return "IE8";
            }
        } // isIE end


        if (isFF) {
            return "FF";
        }

        if (isOpera) {
            return "Opera";
        }

        if (isSafari) {
            return 'Safari'
        }

        if (isChrome) {
            return "Chrome";
        }
    };

    /**
     * 复位以便重用 XmlHttpRequest 对象
     */
    utils.reuseXmlHttpRequest = function(xmlHttp) {
        // 如果状态为 UNSENT(初始状态，未打开) 则可以直接使用
        if (xmlHttp.readyState == 0) {
            return xmlHttp;
        }

        // 如果状态为 DONE(已完成)，则可能要处理一下再复用
        if (xmlHttp.readyState == 4) {
            try {
                xmlHttp.onloadstart = null; // 当程序开始加载时，loadstart 事件将被触发。
                xmlHttp.onprogress = null; // 进度事件会被触发用来指示一个操作正在进行中。
                xmlHttp.onabort = null; // 当一个资源的加载已中止时，将触发 abort 事件。
                xmlHttp.onerror = null; // 当一个资源加载失败时会触发error事件。
                xmlHttp.onload = null; // 当一个资源及其依赖资源已完成加载时，将触发load事件。
                xmlHttp.ontimeout = null; // 当进度由于预定时间到期而终止时，会触发timeout 事件。
                xmlHttp.onloadend = null; // 当一个资源加载进度停止时 (例如，在已经分派“错误”，“中止”或“加载”之后)，触发loadend事件。
                xmlHttp.onreadystatechange = null; // 事件会在 readyState 属性发生变化时触发。
                xmlHttp.abort(); // ?
                return xmlHttp;
            } catch (e) {
                console.log("复位并重用 XMLHttpRequest 对象时出错！" + e.toString());
                return null;
            }
        }

        console.log("XMLHttpRequest 对象当前正在使用，不允许被复用！");
        return null;
    }

    /**
     * 获取 XMLHttpRequest 对象
     * @param {type} reuse 如果可能就重用对象
     */
    utils.getXMLHttpRequest = function(reuse) {
        var xmlHttp;

        // 初始化缓冲区
        if (utils.xmlHttpCache == null) {
            utils.xmlHttpCache = [];
        }

        if (reuse) {
            // 在缓冲区中找可重用的
            for (xmlHttp in utils.xmlHttpCache) {
                // 如果状态为 UNSENT(初始状态，未打开) 则可以直接使用
                if (xmlHttp.readyState == 0) {
                    return xmlHttp;
                }
                // 如果状态为 DONE(已完成)，则可能要处理一下再复用
                if (xmlHttp.readyState == 4) {
                    return utils.reuseXmlHttpRequest(xmlHttp);
                }
            }
        }

        // 新建一个
        // Provide the XMLHttpRequest class for IE 5.x-6.x:
        // Other browsers (including IE 7.x-8.x) ignore this when XMLHttpRequest is predefined
        if (typeof XMLHttpRequest != "undefined") {
            xmlHttp = new XMLHttpRequest();
        } else if (window.ActiveXObject) {
            var aVersions = ["Msxml2.XMLHttp.5.0", "Msxml2.XMLHttp.4.0", "Msxml2.XMLHttp.3.0", "Msxml2.XMLHttp",
                "Microsoft.XMLHttp"
            ];
            for (var i = 0; i < aVersions.length; i++) {
                try {
                    xmlHttp = new ActiveXObject(aVersions[i]);
                    break;
                } catch (e) {}
            }
        }

        // 放入缓冲区
        utils.xmlHttpCache[utils.xmlHttpCache.length] = xmlHttp;
        return xmlHttp;
    }

    /**
     * 获取窗口默认大小
     * @param {Function} 回调函数
     */
    utils.getScreenSize = function(onSizeCallback) {
        // 如果使用uniapp开始APP端和微信端 在调试的时候
        if (typeof uni == 'object') {
            uni.getSystemInfo({
                success: function success(res) {
                    onSizeCallback({
                        width: res.screenWidth,
                        height: res.screenHeight
                    });
                }
            });
        } else if (typeof wx == 'object') {
            wx.getSystemInfo({
                success: function success(res) {
                    onSizeCallback({
                        width: res.windowWidth,
                        height: res.windowHeight
                    });
                }
            });
        } else if (typeof window != 'undefined' && typeof document != 'undefined') {
            var width = document.documentElement.clientWidth || document.body.clientWidth;
            var height = document.documentElement.clientHeight || document.body.clientHeight;
            onSizeCallback({
                width: width,
                height: height
            });
        }
    };

    const TEL_REGEXP = /^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\d{8}$/;

    /**
     * 手机号码正则表达式
     */
    utils.validateTel = function(tel) {
        return TEL_REGEXP.test(tel);
    };

    // 网关ID正则表达式
    const GWID_REGEXP = /[0-9A-Z]{15}/;

    /**
     * 网关ID正则表达式校验
     */
    utils.validateGwid = function(gwid) {
        return GWID_REGEXP.test(gwid);
    };

    // url 正则表达式
    const URL_REGEXP = /^((ht|f)tps?):\/\/([\w\-]+(\.[\w\-]+)*\/)*[\w\-]+(\.[\w\-]+)*\/?(\?([\w\-\.,@?^=%&:\/~\+#]*)+)?/;

    /**
     * url 正则表达式校验
     */
    utils.validateURL = function(url) {
        return URL_REGEXP.test(url);
    };

    const IP_REGEXP = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/; //正则表达式
    utils.validateIP = function(ip) {
        if (IP_REGEXP.test(ip)) {
            if (RegExp.$1 < 256 && RegExp.$2 < 256 && RegExp.$3 < 256 && RegExp.$4 < 256)
                return true;
        }
        return false;
    }


    /**
     * 获取字符串中的实际长度（非length）而是字节长度
     * @param {String} str
     */
    utils.getByteLengthByStr = function(str) {
        // 如果参数str 是number类型的  要转成string类型  因为number是没有length属性的
        // 而且不能使用 str instanceof Number去判断是否为Number类型 因为这个是判断对象类型的  一般情况下‘数字’都是基本类型 没多少人使用 new Number(1)
        // 就像一般不适用integer 而是使用int一样
        if (typeof str == 'number') {
            str = str + '';
        }
        var realLength = 0,
            len = str.length,
            charCode = -1;
        for (var i = 0; i < len; i++) {
            charCode = str.charCodeAt(i);
            if (charCode >= 0 && charCode <= 128) realLength += 1;
            else realLength += 2;
        }
        return realLength;
    };

    /**
     * 获取宽高 或者根据传递的宽高  获取相应的宽高
     * @param {Number} w 最大宽度
     * @param {Number} h 最大高度
     */
    utils.initUiPanelViewSize = function(w, h) {
        var windowWidth, windowHeight, canvasHeight, canvasWidth, scale, marginTop, offsetLeft, ctrlWidth, ctrlHeigth,
            isLandscape;

        // 可视有效的尺寸
        windowWidth = w ? w : Math.floor(document.body.clientWidth);
        windowHeight = h ? h : Math.floor(document.body.clientHeight);

        // 判断当前页面宽高是否为横屏时的宽高
        isLandscape = windowWidth > windowHeight;

        // 针对横屏或者竖屏有着不同的显示方式
        if (isLandscape) {
            // 如果是横屏
            // console.log('横屏');
            canvasHeight = windowHeight;
            canvasWidth = canvasHeight * 16 / 9;

            // 设置缩放比例
            scale = windowHeight / 360;

            // 如果计算出来的画布宽度大于了实际的宽度 那么要重新处理
            if (canvasWidth > windowWidth) {
                canvasWidth = windowWidth;
                canvasHeight = canvasWidth * 9 / 16;
                scale = canvasHeight / 360;
            }
        } else {
            // 如果是竖屏
            // console.log('竖屏');
            canvasHeight = windowHeight;
            canvasWidth = canvasHeight * 9 / 16;

            // 设置缩放比例
            scale = windowHeight / 640;

            // 如果计算出来的画布高度大于了实际的高度 那么要重新处理
            if (canvasWidth > windowWidth) {
                canvasWidth = windowWidth;
                canvasHeight = canvasWidth * 16 / 9;
                scale = canvasHeight / 640;
            }
        }

        // 向下取整
        canvasHeight = Math.floor(canvasHeight);
        canvasWidth = Math.floor(canvasWidth);
        marginTop = (windowHeight - canvasHeight) / 2;
        offsetLeft = (windowWidth - canvasWidth) / 2;

        // 生成一个子面板显示的即时宽高
        ctrlWidth = Math.floor(canvasWidth * 0.5);
        ctrlHeigth = Math.floor(canvasHeight * 0.5);

        // 返回值
        return {
            windowWidth: windowWidth,
            windowHeight: windowHeight,
            canvasHeight: canvasHeight,
            canvasWidth: canvasWidth,
            scale: scale,
            marginTop: marginTop,
            offsetLeft: offsetLeft,
            ctrlWidth: ctrlWidth,
            ctrlHeigth: ctrlHeigth,
            isLandscape: isLandscape
        };
    };

    /**
     * 模仿 C/Java 中的字符串格式化
     * @param {String} fmt: 待格式化字符串，支持 %s, %d, %f
     * @param {Object} arguments: 变长参数
     */
    utils.format = function(fmt) {
        if (typeof fmt != 'string') {
            return null;
        }

        // 将 arguments 转化为数组（ES5中并非严格的数组）
        var argus = arguments.slice(1);
        var args = Array.prototype.slice.call(argus);
        var count = 0;

        // 通过正则替换格式化字段
        return fmt.replace(/%[sdf]/g, function(pattern, offset) {
            // 判断：
            // 1. 如果args[count]为空，则返回‘null’
            // 2. 如果args[count]非空，则判断是否为Object类型同时非函数类型，是则返回对应的JSON 字符串；否则为基本类型，返回toString();
            // ps 如果是函数类型，那么返回函数本身打印出来的就是函数的内容字符串。
            let field = "";
            if ((args[count] == null)) {
                field = 'null'
            } else if (args[count] instanceof Object && !(args[count] instanceof Function)) {
                field = JSON.stringify(args[count]);
            } else {
                field = args[count].toString();
            }
            // let field = (args[count] == null) ? 'null' : ((args[count] instanceof Object && !(args[count] instanceof Function) ? JSON.stringify(args[count]) : args[count].toString()); 
            count++;
            return field;
        });
    };

    // 把模块放到全局命名空间中去
    ;
    (function() {
        return typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ?
            window : {};
    })()["utils"] = utils;
}